/*
 * TOP SECRET Copyright 2006-2015 Transsion.com All right reserved. This software is the confidential and proprietary
 * information of Transsion.com ("Confidential Information"). You shall not disclose such Confidential Information and
 * shall use it only in accordance with the terms of the license agreement you entered into with Transsion.com.
 */
package com.yunji.framework_template.web.service;

import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;

import javax.validation.constraints.NotNull;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.google.common.collect.Lists;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixCommand;
import com.netflix.hystrix.contrib.javanica.annotation.HystrixProperty;

import com.yunji.framework_template.common.constant.Constant;
import com.yunji.framework_template.orm.persistence.jpa.TestDao;
import com.yunji.framework_template.orm.persistence.model.Test;
import com.yunji.framework_template.web.enums.ResponseType;
import com.yunji.framework_template.web.enums.SortOrder;
import com.yunji.framework_template.web.service.pojo.request.TestQueryById;
import com.yunji.framework_template.web.service.pojo.response.ResultType;
import com.yunji.framework_template.web.service.pojo.response.TestFullResult;
import com.yunji.framework_template.web.service.pojo.response.TestOnlyIdAndValResult;
import com.yunji.framework_template.web.service.pojo.response.TestOnlyIdResult;
import com.yunji.framework_template.web.service.pojo.vo.TestOnlyIdVO;
import com.yunji.framework_template.web.service.pojo.vo.TestVO;
import lombok.extern.slf4j.Slf4j;

/**
 * ClassName:TestService <br/>
 * Date: 2018年9月25日 下午4:23:39 <br/>
 * 
 * @author fenglibin1982@163.com
 * @Blog http://blog.csdn.net/fenglibing
 * @version
 * @see
 */
@Slf4j
@Service
@CacheConfig(cacheNames = Constant.CACHE_PREFIX + "test")
public class TestService {

    @Autowired
    private TestDao testDao;

    @Cacheable(key = "{#root.methodName}")
    public Test findFirstBy() {
        return testDao.findFirstBy();
    }

    public List<Test> findByVal(String val) {
        return testDao.findByVal(val);
    }

    public Page<Test> findByPage(int page, int size, SortOrder sortOrder) {
        Sort sort = null;
        if (SortOrder.DESC == sortOrder) {
            sort = Sort.by("id").descending();
        } else {
            sort = Sort.by("id").ascending();
        }
        Pageable pageable = PageRequest.of(page, size, sort);
        return testDao.findAll(pageable);
    }

    /**
     * Cacheable注解中的key使用的是SpEL(Spring表代式语言)，此处用于动态计算key，该key表示的是缓存所使用的Key。 如果没有指定{@link #keyGenerator}，key的默认值是""。
     * SpEL表达式针对提供以下元数据的专用上下文进行评估： #root.method，＃root.target和#root.caches分别用于引用方法，目标对象和受影响的缓存，
     * ＃root.methodName，#root.targetClass分别用于引用方法名称和类名称。
     * 参数的引用可以通过#root.args[1]，＃p1或＃a1访问第二个参数，其索引是从0开始的，另外也可以通过参数名称进行访问。 在该示例中，展示的是通过方法名称和第一个参数做为key。 另：
     * 1）如果参数传入的是一个对象，可以通过"?"获取其后面的参数，假设传入的参数有id属性，则获取其id时通过表达式"#p0?.id" 或者 "#bigType.id"获取 2）多个参数的表达式示例：@Cacheable(key
     * = "{#root.methodName, #p0?.id,#p1}")
     * 3）Cache注解的使用详情，参看文档：https://docs.spring.io/spring/docs/current/spring-framework-reference/integration.html#cache-annotations-caching
     * 
     * @param bigType
     * @return
     */
    @Cacheable(key = "{#root.methodName, #p0}")
    public List<Test> findByBigType(@NotNull String bigType) {
        log.info("findByBigType called");
        return testDao.findByBigType(bigType);
    }

    @Cacheable(key = "{#root.methodName, #p0}")
    public List<Test> findByBigTypeOrderByModified(@NotNull String bigType) {
        return testDao.findByBigTypeOrderByModified(bigType);
    }

    /**
     * 查出来后，通过filter去掉空记录以及smallType为空的记录，然后根据时间再排序，再返回排序后的列表
     * 
     * @param bigType
     * @return
     */
    public List<Test> findByBigTypeSortByModified(@NotNull String bigType) {
        return testDao.findByBigType(bigType).stream().filter((obj) -> obj != null).filter((obj) -> obj.getSmallType() != null).sorted((o1,
                                                                                                                                        o2) -> {
            if (o1.getModified().getTime() > o2.getModified().getTime()) {
                return 1;
            } else if (o1.getModified().getTime() == o2.getModified().getTime()) {
                return 0;
            } else {
                return -1;
            }
        }).collect(Collectors.toList());
    }

    public List<Test> findByBigTypeAndSmallType(@NotNull String bigType, @NotNull String smallType) {
        return testDao.findByBigTypeAndSmallType(bigType, smallType);
    }

    /**
     * 更新对象，并且更新成功后清除如果存在的缓存.并将更新后的结果返回
     * 
     * @param test
     * @return
     */
    @CacheEvict(key = "{#p0.id}")
    @Transactional
    public Test add(Test test) {
        return testDao.save(test);
    }

    public List<Test> searchAll() {
        return testDao.searchAll();
    }

    /**
     * 返回按指定字段排序后的结果
     * 
     * @return
     */
    public List<Test> searchAllSortByModified() {
        return testDao.findAll(Sort.by(Sort.Order.desc("modified")));
    }

    /**
     * 对返回的对象重新排序，且只返回必须的字段
     * 
     * @return
     */
    public List<TestVO> searchAllSortByModifiedVO() {
        return Lists.transform(testDao.findAll(Sort.by(Sort.Order.desc("modified"))), (obj) -> {
            TestVO testVO = new TestVO();
            testVO.setId(obj.getId());
            testVO.setVal(obj.getVal());
            return testVO;
        });
    }

    /**
     * 根据对象id进行查询，并缓存查询后的结果
     * 
     * @param id
     * @return
     */
    @Cacheable(key = "{#p0}")
    public Optional<Test> selectById(@NotNull Integer id) {
        return Optional.of(testDao.searchById(id));
    }

    /**
     * 根据对象id进行查询，并缓存查询后的结果
     * 
     * @param id
     * @return
     */
    @Cacheable(key = "{#p0}")
    public ResultType selectByIdAndResponseType(@NotNull TestQueryById testVO) {
        ResultType result = null;
        Test test = testDao.searchById(testVO.getId());
        if (ResponseType.ALL == testVO.getResponseType()) {
            result = getFullResult(test);
        } else if (ResponseType.ONLY_ID == testVO.getResponseType()) {
            result = getOnlyIdResult(test);
        } else if (ResponseType.ONLY_ID_VAL == testVO.getResponseType()) {
            result = getOnlyIdAndValResult(test);
        }
        return result;
    }

    private ResultType getOnlyIdResult(Test test) {
        TestOnlyIdResult result = new TestOnlyIdResult();
        TestOnlyIdVO resultVO = new TestOnlyIdVO();
        resultVO.setId(test.getId());
        result.setTest(resultVO);
        return result;
    }

    private ResultType getFullResult(Test test) {
        TestFullResult result = new TestFullResult();
        result.setTest(test);
        return result;
    }

    private ResultType getOnlyIdAndValResult(Test test) {
        TestOnlyIdAndValResult result = new TestOnlyIdAndValResult();
        TestVO resultVO = new TestVO();
        resultVO.setId(test.getId());
        resultVO.setVal(test.getVal());
        result.setTest(resultVO);
        return result;
    }

    /**
     * 删除数据库的记录后，并同时删除缓存
     * 
     * @param id
     */
    @CacheEvict(key = "{#p0}")
    public void delete(@NotNull Integer id) {
        testDao.deleteById(id);
    }

    @HystrixCommand(commandProperties = {
            @HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
            @HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") },
			fallbackMethod = "reliable")
	public boolean doTask() throws InterruptedException {
		// 执行后续的业务逻辑
		Thread.sleep(3000);
		int a=1;
		if(a==1) {
			throw new RuntimeException("test exception");
		}
		return true;
	}
	public boolean reliable(String name) {
        return false;
    }
	
	@HystrixCommand(commandProperties = {
			@HystrixProperty(name = "circuitBreaker.requestVolumeThreshold", value = "10"),
			@HystrixProperty(name = "execution.isolation.thread.timeoutInMilliseconds", value = "2000") }, fallbackMethod = "otherCount")
	public int count() throws InterruptedException {
		Thread.sleep(3000);
		return 10;
	}

	public int otherCount() {
		return -1;
	}
}
